use libc::free;
use std::alloc::{alloc, dealloc, realloc, Layout};
use std::ffi::CString;
use std::ffi::{CStr, CString};
use std::sync::atomic::{AtomicUsize, Ordering};
#[repr(C)]
type TrieValue = *mut std::ffi::c_void;
const TRIE_NULL: TrieValue = std::ptr::null_mut();
const NUM_TEST_VALUES: usize = 10000;
const LONG_STRING_LEN: usize = 4096;
macro_rules! assert {
    ($ expr : expr) => {
        num_assert.fetch_add(1, Ordering::SeqCst);
        if !$expr {
            panic!("Assertion failed: {}", stringify!($expr));
        }
    };
}
static num_assert: AtomicUsize = AtomicUsize::new(0);
static test_array: [i32; NUM_TEST_VALUES] = [0; NUM_TEST_VALUES];
static test_strings: [[u8; 10]; NUM_TEST_VALUES] = [[0; 10]; NUM_TEST_VALUES];
static bin_key: [u8; 7] = [b'a', b'b', b'c', 0, 1, 2, 0xff];
static bin_key2: [u8; 8] = [b'a', b'b', b'c', 0, 1, 2, 0xff, 0];
static bin_key3: [u8; 3] = [b'a', b'b', b'c'];
static bin_key4: [u8; 4] = [b'z', 0, b'z', b'z'];
type UnitTestFunction = fn();
static tests: [UnitTestFunction; 11] = [
    test_trie_new_free,
    test_trie_insert,
    test_trie_lookup,
    test_trie_remove,
    test_trie_replace,
    test_trie_insert_empty,
    test_trie_free_long,
    test_trie_negative_keys,
    test_trie_insert_binary,
    test_trie_insert_out_of_memory,
    test_trie_remove_binary,
];
#[repr(C)]
fn generate_trie() -> *mut Trie {
    unsafe {
        let trie = trie_new();
        assert!(
            trie_insert_binary(
                trie,
                bin_key2.as_ptr(),
                bin_key2.len(),
                CString::new("goodbye world").unwrap().as_ptr()
            ) != 0
        );
        assert!(
            trie_insert_binary(
                trie,
                bin_key.as_ptr(),
                bin_key.len(),
                CString::new("hello world").unwrap().as_ptr()
            ) != 0
        );
        trie
    }
}
#[repr(C)]
#[test]
fn test_trie_new_free() {
    let long_string: *mut i8 = malloc(LONG_STRING_LEN) as *mut i8;
    memset(
        long_string as *mut libc::c_void,
        'A' as i32,
        LONG_STRING_LEN,
    );
    *long_string.add(LONG_STRING_LEN - 1) = 0;
    let trie: *mut Trie = trie_new();
    trie_insert(trie, long_string, long_string);
    trie_free(trie);
    free(long_string as *mut libc::c_void);
}
#[test]
fn test_trie_lookup() {
    let mut buf = String::with_capacity(10);
    let mut val: *mut i32;
    let mut i: i32;
    let trie = generate_trie();
    assert_eq!(
        trie_lookup(trie, b"000000000000000\0".as_ptr() as *const i8),
        TRIE_NULL
    );
    assert_eq!(trie_lookup(trie, b"\0".as_ptr() as *const i8), TRIE_NULL);
    for i in 0..NUM_TEST_VALUES {
        buf.clear();
        std::fmt::write(&mut buf, format_args!("{}", i)).unwrap();
        let buf_cstr = std::ffi::CString::new(buf.as_bytes()).unwrap();
        val = trie_lookup(trie, buf_cstr.as_ptr()) as *mut i32;
        assert_eq!(unsafe { *val }, i);
    }
    trie_free(trie);
}
#[test]
fn test_trie_remove() {
    let trie: *mut Trie;
    let mut value: *mut std::ffi::c_void;
    trie = generate_binary_trie();
    value = unsafe { trie_lookup_binary(trie, bin_key3.as_ptr() as *const _, bin_key3.len()) };
    assert!(value.is_null());
    assert!(
        unsafe { trie_remove_binary(trie, bin_key3.as_ptr() as *const _, bin_key3.len()) } == 0
    );
    assert!(
        unsafe { trie_lookup_binary(trie, bin_key4.as_ptr() as *const _, bin_key4.len()) } as usize
            == 0
    );
    assert!(
        unsafe { trie_remove_binary(trie, bin_key4.as_ptr() as *const _, bin_key4.len()) } == 0
    );
    assert!(
        unsafe { trie_remove_binary(trie, bin_key2.as_ptr() as *const _, bin_key2.len()) } != 0
    );
    assert!(
        unsafe { trie_lookup_binary(trie, bin_key2.as_ptr() as *const _, bin_key2.len()) }
            .is_null()
    );
    assert!(
        !unsafe { trie_lookup_binary(trie, bin_key.as_ptr() as *const _, bin_key.len()) }.is_null()
    );
    assert!(unsafe { trie_remove_binary(trie, bin_key.as_ptr() as *const _, bin_key.len()) } != 0);
    assert!(
        unsafe { trie_lookup_binary(trie, bin_key.as_ptr() as *const _, bin_key.len()) }.is_null()
    );
    unsafe { trie_free(trie) };
}
#[test]
fn test_trie_insert_empty() {
    let mut trie: *mut Trie;
    unsafe {
        trie = generate_binary_trie();
        alloc_test_set_limit(3);
        assert!(
            trie_insert_binary(
                trie,
                bin_key4.as_ptr() as *const i8,
                bin_key4.len(),
                "test value".as_ptr() as *const i8
            ) == 0
        );
        assert!(trie_lookup_binary(trie, bin_key4.as_ptr() as *const i8, bin_key4.len()).is_null());
        assert!(trie_num_entries(trie) == 2);
        trie_free(trie);
    }
}
#[test]
fn test_trie_negative_keys() {
    let my_key = MY_KEY.as_ptr();
    unsafe {
        let trie = trie_new();
        assert!(trie_insert(trie, my_key, b"hello world\0".as_ptr() as *const i8) != 0);
        let value = trie_lookup(trie, my_key);
        assert!(!libc::strcmp(value, b"hello world\0".as_ptr() as *const i8) != 0);
        assert!(trie_remove(trie, my_key) != 0);
        assert!(trie_remove(trie, my_key) == 0);
        assert!(trie_lookup(trie, my_key).is_null());
        trie_free(trie);
    }
}
fn main() {
    run_tests(tests);
    println!("num_assert: {}", num_assert);
}
